几十亿数据查询3秒返回,ES性能优化实战!
公司的数据平台已迭代三个版本,从头开始遇到很多常见的难题,终于有时间整理一些完善的文档,在此分享以供所需的朋友。
图片来自 Pexels
在此篇幅中偏重于 ES 的优化,关于 HBase,Hadoop 的设计优化有很多文章可以参考,不再赘述。
需求说明
项目背景:在一业务系统中,部分表每天的数据量过亿,已按天分表,但业务上受限于按天查询,并且 DB 中只能保留 3 个月的数据(硬件高配),分库代价较高。
改进版本目标:
数据能跨月查询,并且支持 1 年以上的历史数据查询与导出。
按条件的数据查询秒级返回。
Elasticsearch 检索原理
①关于 ES 和 Lucene 基础结构
先从 ES 的基础结构说起(如下图):
Cluster 包含多个 Node 的集群。
Node 集群服务单元。
Index 一个 ES 索引包含一个或多个物理分片,它只是这些分片的逻辑命名空间。
Type 一个 index 的不同分类,6.x 后只能配置一个 Type,以后将移除。
Document 最基础的可被索引的数据单元,如一个 JSON 串。
Shards 一个分片是一个底层的工作单元,它仅保存全部数据中的一部分,它是一个 Lucence 实例。
一个 Lucene 索引最大包含 2,147,483,519 (= Integer.MAX_VALUE - 128)个文档数量。
Replicas 分片备份,用于保障数据安全与分担检索压力。
ES 依赖一个重要的组件 Lucene,关于数据结构的优化通常来说是对 Lucene 的优化,它是集群的一个存储与检索工作单元,结构如下图:
通过 Luke 工具查看 ES 的 Lucene 文件如下,主要增加了 _id 和 _source 字段:
②Lucene 索引实现
如下图:
整理来源于 Lucene 官方:
http://lucene.apache.org/core/7_2_1/core/org/apache/lucene/codecs/lucene70/package-summary.html#package.description
Solr docs 对此的解释如下:
For other features that we now commonly associate with search, such as sorting, faceting, and highlighting, this approach is not very efficient. The faceting engine,
for example, must look up each term that appears in each document that will make up the result set and pull the document IDs in order to build the facet list. In Solr, this is maintained in memory, and can be slow to load (depending on the number of documents, terms, etc.)
③关于 ES 索引与检索分片
数据具体被存储到哪个分片上:
shard = hash(routing) % number_of_primary_shards
优化案例
ES 仅提供字段的检索,仅存储 HBase 的 Rowkey 不存储实际数据。
实际数据存储在 HBase 中,通过 Rowkey 查询,如下图。
提高索引与检索的性能建议,可参考官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html
一些细节优化项官方与其他的一些文章都有描述,在此文章中仅提出一些本案例的重点优化项。
优化索引性能
为了减少对其他操作的影响(如检索),Elasticsearch 进行阈值限制,默认是 20MB/s,可配置的参数(根据磁盘性能调整):
"indices.store.throttle.max_bytes_per_sec" : "200mb"
合并线程数默认是:
Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))
如果是机械磁盘,可以考虑设置为 1:
index.merge.scheduler.max_thread_count: 1
优化检索性能
②尽量使用 keyword 替代一些 long 或者 int 之类,term 查询总比 range 查询好 (参考 Lucene 说明 )。
http://lucene.apache.org/core/7_4_0/core/org/apache/lucene/index/PointValues.html
from+size:每分片检索结果数最大为 from+size,假设 from=20,size=20,则每个分片需要获取 20*20=400 条数据。
多个分片的结果在协调节点合并(假设请求的分配数为 5,则结果数最大为 400*5=2000 条)再在内存中排序后,然后 20 条给用户。
index.max_result_window :10000
search_after:使用前一个分页记录的最后一条来检索下一个分页记录。
在我们的案例中,首先使用 from+size,检索出结果后再使用 search_after,在页面上我们限制了用户只能跳 5 页,不能跳到最后一页。
scroll:用于大结果集查询,缺陷是需要维护 scroll_id。
"merge.policy.expunge_deletes_allowed": "0"
{
"mappings": {
"data": {
"dynamic": "false",
"_source": {
"includes": ["XXX"] -- 仅将查询结果所需的数据存储仅_source中
},
"properties": {
"state": {
"type": "keyword", -- 虽然state为int值,但如果不需要做范围查询,尽量使用keyword,因为int需要比keyword增加额外的消耗。
"doc_values": false -- 关闭不需要字段的doc values功能,仅对需要排序,汇聚功能的字段开启。
},
"b": {
"type": "long" -- 使用了范围查询字段,则需要用long或者int之类 (构建类似KD-trees结构)
}
}
}
},
"settings": {......}
}
性能测试
单节点 5000 万到 1 亿的数据量测试,检查单点承受能力。
集群测试 1 亿-30 亿的数量,磁盘 IO/内存/CPU/网络 IO 消耗如何。
随机不同组合条件的检索,在各个数据量情况下表现如何。
另外 SSD 与机械盘在测试中性能差距如何。
生产效果
作者:mikevictor
编辑:陶家龙、孙淑娟
出处:https://www.cnblogs.com/mikevictor07/p/10006553.html
精彩文章推荐: